package de.fhaugsburg.games.boardgames.scrabble.logging;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;
import java.util.Date;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import de.fhaugsburg.games.boardgames.exceptions.GameException;
import de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine;

public class LoggingHandler implements InvocationHandler {

	private static final Level LOGLEVEL = Level.FINER;
	
	private IScrabbleEngine engine;
	private Logger logger;	
		
	/**
	 * constructor, registers engine
	 * @param engine
	 */
	public LoggingHandler(IScrabbleEngine engine, Logger logger) {
		this.engine = engine;
		this.logger = logger;
	}
	
	
	/**
	 * factory method to initialize the nasty logger/proxy stuff
	 * @param engine
	 * @return
	 */
	public static IScrabbleEngine scrabbleEngineLogginProxy (IScrabbleEngine engine){
		Logger logger; 
		try {
	      // This block configure the logger with handler and formatter
		  logger = Logger.getLogger(IScrabbleEngine.class.getName());
		  //we do not want logging on our console thru the default/parent handler
		  logger.setUseParentHandlers(false);
	      //no logging to console
	      ConsoleHandler ch = new ConsoleHandler();
	      ch.setLevel(Level.OFF);
	      logger.addHandler(ch);
		  //log to file
	      FileHandler fh = new FileHandler(System.getProperty("user.dir") + "/logs/scrabble.log", true);
	      fh.setLevel(Level.ALL);
	      SimpleFormatter formatter = new SimpleFormatter();
	      fh.setFormatter(formatter);
	      logger.addHandler(fh);
	      //log everything
	      logger.setLevel(LOGLEVEL);

	      logger.log(Level.INFO, "scrabble launched, starting logging session " + new Date());
	    
		  LoggingHandler handler = new LoggingHandler(engine, logger);
		  
		  //instantiate the pr0xy
	      IScrabbleEngine engineProxy = (IScrabbleEngine) Proxy.newProxyInstance(
									engine.getClass().getClassLoader(),
						            engine.getClass().getInterfaces(),
						            handler);
		
			//returns the entire proxy (damn ugly, but very usable :)
			return engineProxy;
		
		} catch (SecurityException e) {
		    e.printStackTrace();
	    } catch (IOException e) {
	    	e.printStackTrace();
	    }
	    throw new RuntimeException("unable to initialize logger");
	}

	
	
	
	/**
	 * invoke method to handle and log transparent proxy calls
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//System.out.print("* calling " + method);

		StringBuilder argString = new StringBuilder();
		if (args != null){
	        for (int i = 0; i < args.length; i++) 
	        	argString.append(" " + args[i]);
		}

		logger.log(Level.FINEST, "called: " + method + " args: " + argString.toString());
		
        Object result = null;
        try  {
            result = method.invoke(engine, args);
      
        } catch(IllegalAccessException ex)  {
            ex.printStackTrace();
        	
        } catch(InvocationTargetException ex)  {
        	//ugly, other method of determining type of exception?
        	Object except = ex.getCause().getClass().getSuperclass().newInstance();
        	if (except instanceof GameException){
        		logger.log(Level.INFO, "* exception: " + ex.getTargetException());
        	}else {
        		StringWriter sw = new StringWriter();
        		ex.printStackTrace(new PrintWriter(sw));
        		logger.log(Level.SEVERE, "* unexpected exception: " + sw.toString());
        	}
        	throw ex.getTargetException();
        }
        
        logger.log(Level.FINEST, "result: " + result);

        logGameHistory();
        
        return result;
	}

	
	
	
	/**
	 * logs some game parameters
	 */
	private void logGameHistory(){
		logger.fine("gameState=" + engine.getGameState().toString());
		
		//TODO: placeholder for logging state-specific events
		switch (engine.getGameState()){
		case INITIAL:
			break;
		case DROPPING:

		case COMMITED:

		case GAMEOVER:
			logger.fine(MessageFormat.format("active player: {0}, pieces: {1}, score: {2}", 
					engine.getActivePlayer().getPlayerName(),
					engine.getActivePlayer().getMyPiecesString(),
					engine.getActivePlayer().getPoints()
					));
			logger.finer(engine.getActivePlayer().getBoard().toString());
			break;
		}
	}
	
}
